using System;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Collections;
using System.Collections.Generic;

// author: Dominic Ullmann, dominic_ullmann@swissonline.ch
// Version: 1.02

// VNC-Client for .NET
// Copyright (C) 2002  Dominic Ullmann

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

namespace VNC.RFBProtocolHandling
{
    /// <summary> 
    /// this class handles sending, receiving and dispatching of VNC-Messages
    /// </summary>
    public class RFBProtocolHandler
    {
        /// <summary> the connection used by the protocol-handler </summary>
        private RFBNetworkStream stream;

        internal RFBNetworkStream Stream
        {
            get { return stream; }
        }

        private RFBTcpClient client;
        private string server;
        private int port;

        /// <summary> the surface preserving the pixeldata at the client </summary>
        //private RFBSurface surface;
        /// <summary> The Thread handing incoming regular protocol messages </summary>
        private Thread handler = null;

        /// <summary> creates a protocol handler, which handels a vnc-connection
        /// </summary>
        /// 
        public RFBProtocolHandler(RFBTcpClient tcpClient)
        {
            client = tcpClient;
            stream = client.GetRFBStream();
        }


        /// <summary> handshake with the vnc-server </summary>
        public ServerData handshake()
        {
            try
            {
                client = new RFBTcpClient();
                client.Connect(server, port); // connect to port on server
                client.NoDelay = true; // sending without delay!
                stream = client.GetRFBStream();
                // connection established!
                // getting the protocol-Message from the Server
                uint serverMajor = 0; uint serverMinor = 0;
                receiveProtocolMessage(out serverMajor, out serverMinor);
                Console.WriteLine("serverMajor : " + serverMajor + " serverMinor : " + serverMinor);

                if (serverMajor > 3)
                {
                    throw new Exception("server Major too big!");
                }

                // sending my protocol-Message to server
                sendProtocolMessage(3, 3); // version 3.3

                // do authentification
                //authenticate(); // throws exception, if failed

                // send client initalization
                sendClientInitialization();

                // server initalization
                ServerData data = receiveServerInitialization();
                // handshake ok!
                return data;

            }
            catch (Exception e)
            {
                try { client.Close(); }
                catch (SocketException) { } // close connection if open!
                Console.WriteLine(e.ToString());
                throw new Exception("handshake failed");
            }
        }

        /// <summary> closing connection to the vnc-server </summary>
        public void closeConnection()
        {
            // shutting down handler
            if (handler != null)
                handler.Abort();
            // closing connection
            client.Close();
            // should not be need, but Thread-Abort doesn't work always
            Environment.Exit(0);
        }




        // ----------------------
        // Handling RFB Handshake
        // ----------------------

        /// <summary>
        /// this methods sends the protocol message to the server and starts
        /// the handshake with the server with it
        /// </summary>
        public void sendProtocolMessage(uint major, uint minor)
        {
            byte[] arr = new byte[12];
            arr[0] = 0x52;
            arr[1] = 0x46;
            arr[2] = 0x42;
            arr[3] = 0x20;
            arr[7] = 0x2e;
            arr[11] = 0x0a;

            // major
            arr[4] = (byte)(((major >> 16) & 0xFF) + 0x30);
            arr[5] = (byte)(((major >> 8) & 0xFF) + 0x30);
            arr[6] = (byte)((major & 0xFF) + 0x30);

            // minor: least sig byte first in int-type
            arr[8] = (byte)(((minor >> 16) & 0xFF) + 0x30);
            arr[9] = (byte)(((minor >> 8) & 0xFF) + 0x30);
            arr[10] = (byte)((minor & 0xFF) + 0x30);

            stream.Write(arr, 0, 12);
            stream.Flush();
        }

        /// <summary>
        /// receive the Protocol Message from the Server
        /// </summary>
        private void receiveProtocolMessage(out uint major, out uint minor)
        {

            byte[] arr = new byte[12];
            stream.ReadBlocking(arr, 0, 12);

            // check
            if ((arr[0] != 0x52) || (arr[1] != 0x46) || (arr[2] != 0x42) ||
                (arr[3] != 0x20) || (arr[7] != 0x2e) || (arr[11] != 0x0a))
            {
                throw new InvalidRFBDataException();
            }
            // convert Version-Numbers
            major = (uint)((arr[4] - 0x30) * 100 + (arr[5] - 0x30) * 10 + (arr[6] - 0x30));
            minor = (uint)((arr[8] - 0x30) * 100 + (arr[9] - 0x30) * 10 + (arr[10] - 0x30));
        }



        /// <summary> getting serverInitalization </summary>
        public ServerData receiveServerInitialization()
        {
            ServerData data = new ServerData();
            data.fbWidth = stream.ReadCard16();
            data.fbHeight = stream.ReadCard16();
            data.pixForm = stream.ReadPixelFormat();
            uint serverNameLength = stream.ReadCard32();
            data.serverName = stream.ReadString(serverNameLength);
            return data;
        }
        /// <summary> sends the client initialization </summary>
        public void sendClientInitialization()
        {
            stream.WriteByte(0); // don't share connection
            stream.Flush();
        }

        // Messages after Initialization

        /// <summary> send setPixelFormat-message </summary>
        public void sendSetPixelFormat(PixelFormat pixFormat)
        {
            Monitor.Enter(stream);

            stream.WriteByte(0); // mgs-type
            for (int i = 0; i < 3; i++) { stream.WriteByte(0); } // padding
            stream.WritePixelFormat(pixFormat);
            stream.Flush();

            Monitor.Exit(stream);
        }


        /// <summary> send framebuffer-Update message: 
        /// implicitly tells server, that the last sent update-messages was processed at the client
        /// </summary>
        public void sendFBIncrementalUpdateRequest(ushort x, ushort y, ushort width, ushort height)
        {
            // incremental update
            sendFBUpdateRequest(x, y, width, height, true);
        }

        /// <summary> send a full update request </summary>
        public void sendFBNonIncrementalUpdateRequest(ushort x, ushort y, ushort width, ushort height)
        {
            sendFBUpdateRequest(x, y, width, height, false);
        }

        /// <summary> send update request, implicitly tells server, that the last sent update-messages was processed at the client </summary>
        private void sendFBUpdateRequest(ushort x, ushort y, ushort width, ushort height, bool incremental)
        {
            Monitor.Enter(stream);

            stream.WriteByte(3); // mgs-type
            if (incremental) { stream.WriteByte(1); } else { stream.WriteByte(0); }
            stream.WriteCard16(x);
            stream.WriteCard16(y);
            stream.WriteCard16(width);
            stream.WriteCard16(height);
            stream.Flush();

            Monitor.Exit(stream);
        }

        /// <summary> inform the server of a pointer event </summary>
        public void sendPointerEvent(byte buttonMask, ushort x, ushort y)
        {
            Monitor.Enter(stream);

            stream.WriteByte(5); // msg-type
            stream.WriteByte(buttonMask);
            stream.WriteCard16(x);
            stream.WriteCard16(y);
            stream.Flush();

            Monitor.Exit(stream);
        }

        /// <summary> inform the server of a key event </summary>
        public void sendKeyEvent(uint keySym, bool pressed)
        {
            Monitor.Enter(stream);

            stream.WriteByte(4); // msg-type
            if (pressed) { stream.WriteByte(1); } else { stream.WriteByte(0); }
            stream.WriteCard16(0); // padding
            stream.WriteCard32(keySym);
            stream.Flush();

            Monitor.Exit(stream);
        }

        /// <summary> inform the server: client has new text in its cutbuffer </summary>
        public void setClientCutText(string text)
        {
            Monitor.Enter(stream);

            stream.WriteByte(6);
            for (int i = 0; i < 3; i++) { stream.WriteByte(0); } // padding
            stream.WriteCard32((uint)text.Length);
            stream.WriteString(text);
            stream.Flush();

            // pasting text at current server insert position:
            System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding();

            byte[] textAsBytes = encode.GetBytes(text);
            for (int i = 0; i < textAsBytes.Length; i++)
            {
                sendKeyEvent(textAsBytes[i], true);
                sendKeyEvent(textAsBytes[i], false);
            }

            Monitor.Exit(stream);
        }


    }

    // *****************************************************************
    // The following two Classes handles the connection to an RFBServer.
    // *****************************************************************

    /// <summary> this class represents a TCP-Client.
    /// </summary>
    public class RFBTcpClient : TcpClient
    {
        RFBNetworkStream stream = null;

        /// <summary> no argument constructor </summary>
        public RFBTcpClient()
            : base()
        {
        }

        /// <summary> get an RFBStream for this connection </summary>
        public RFBNetworkStream GetRFBStream()
        {
            if (stream == null)
                stream = new RFBNetworkStream(this);
            return stream;
        }

    }

    /// <summary>
    /// interface specifing the functionality of a stream, from which byte could be read.
    ///	</summary>
    /// <remarks>
    /// This interface is used for the PixelDecoders, because they should support decoding pixels from all streams,
    /// which allow reading bytes, e.g. RFBNetworkStream or InflateStream (stream for reading zlib compressed data)
    /// </remarks>
    public interface ReadByteStream
    {
        /// <summary> reading a single byte </summary>
        int ReadByte();
    }

    /// <summary>
    /// this class represents a Network stream, knowing the VNC-Datatypes
    /// </summary>
    public class RFBNetworkStream : ReadByteStream
    {

        private byte[] rfbWriteBuffer = new byte[4]; // for efficient writeop's
        private BufferedStream writeBufferedStream;
        private BufferedStream readBufferedStream;

        /// <summary> constructs an RFBStream for the connection represented by the TCPClient client </summary>
        /// <param name="client">the connection</param>	
        public RFBNetworkStream(TcpClient client)
        {
            NetworkStream stream = client.GetStream();
            writeBufferedStream = new BufferedStream(stream); // using a bufferedstream for writing
            readBufferedStream = new BufferedStream(stream, 20000); // using a bufferedstream for reading, using large buffer!
        }

        /// <summary> blocking readByte: returns exactly one byte </summary>
        public int ReadByte()
        {
            return readBufferedStream.ReadByte();
        }

        /// <summary> blocking reading of multiple bytes </summary>
        public int ReadBlocking(byte[] buffer, int offset, int size)
        {

            for (int i = 0; i < size; i++)
            {
                buffer[offset + i] = (byte)ReadByte();
            }

            return size;
        }

        /// <summary> reading without blocking, returns after at most size bytes have been read </summary>
        public int Read(byte[] buffer, int offset, int size)
        {
            // non blocking Read of multiple bytes
            return readBufferedStream.Read(buffer, offset, size);
        }

        // ----------------------------
        // Reading primitive Data types
        // ----------------------------
        /// <summary> read a card16 from stream </summary>
        public ushort ReadCard16()
        {
            return (ushort)((ReadByte() << 8) + ReadByte());
        }
        /// <summary> read a card32 from stream </summary>
        public uint ReadCard32()
        {
            return (uint)((ReadByte() << 24) + (ReadByte() << 16) +
                (ReadByte() << 8) + ReadByte());
        }
        /// <summary> read a pixelformat from stream </summary>
        public PixelFormat ReadPixelFormat()
        {
            PixelFormat pixForm = new PixelFormat();
            pixForm.bitsPerPixel = (byte)ReadByte();
            pixForm.depth = (byte)ReadByte();
            pixForm.bigEndian = (byte)ReadByte();
            pixForm.trueColor = (byte)ReadByte();
            pixForm.redMax = ReadCard16();
            pixForm.greenMax = ReadCard16();
            pixForm.blueMax = ReadCard16();
            pixForm.redShift = (byte)ReadByte();
            pixForm.greenShift = (byte)ReadByte();
            pixForm.blueShift = (byte)ReadByte();
            for (int i = 0; i < 3; i++) { ReadByte(); } // padding
            return pixForm;
        }
        /// <summary> read a string with the given length from stream </summary>
        public string ReadString(uint length)
        {
            byte[] msg = new byte[length];
            ReadBlocking(msg, 0, msg.Length);
            return System.Text.Encoding.ASCII.GetString(msg);
        }

        /// <summary>
        /// flushing the stream: RFBStream uses a buffer to enhance network throughoutput
        /// this method flushes the buffer
        /// </summary>
        public void Flush()
        {
            writeBufferedStream.Flush();
        }

        // ----------------------------
        // writing primitive data types
        // ----------------------------
        /// <summary> write a byte to the stream </summary>
        public void WriteByte(byte value)
        {
            rfbWriteBuffer[0] = value;
            writeBufferedStream.Write(rfbWriteBuffer, 0, 1);
        }
        /// <summary> write a number of bytes from a byte array to the stream </summary>
        public void Write(byte[] buffer, int offset, int size)
        {
            // writing to bufferedStream
            writeBufferedStream.Write(buffer, offset, size);
        }
        /// <summary> write a card16 to the stream </summary>
        public void WriteCard16(ushort value)
        {
            rfbWriteBuffer[0] = (byte)((value >> 8) & 0xFF);
            rfbWriteBuffer[1] = (byte)(value & 0xFF);
            Write(rfbWriteBuffer, 0, 2);
        }
        /// <summary> write a card32 to the stream </summary>
        public void WriteCard32(uint value)
        {
            rfbWriteBuffer[0] = (byte)((value >> 24) & 0xFF);
            rfbWriteBuffer[1] = (byte)((value >> 16) & 0xFF);
            rfbWriteBuffer[2] = (byte)((value >> 8) & 0xFF);
            rfbWriteBuffer[3] = (byte)(value & 0xFF);
            Write(rfbWriteBuffer, 0, 4);
        }
        /// <summary> wirte a string to the stream </summary>		
        public void WriteString(string text)
        {
            Write(System.Text.Encoding.ASCII.GetBytes(text), 0, text.Length);
        }
        /// <summary> write a pixelformat to the stream </summary>
        public void WritePixelFormat(PixelFormat format)
        {
            WriteByte(format.bitsPerPixel);
            WriteByte(format.depth);
            WriteByte(format.bigEndian);
            WriteByte(format.trueColor);
            WriteCard16(format.redMax);
            WriteCard16(format.greenMax);
            WriteCard16(format.blueMax);
            WriteByte(format.redShift);
            WriteByte(format.greenShift);
            WriteByte(format.blueShift);
            for (int i = 0; i < 3; i++) { WriteByte(0); } // padding
        }

    }

    /// <summary> Exception class for invalid data</summary>
    public class InvalidRFBDataException : System.ApplicationException
    {
        /// <summary> error string </summary>
        public override String ToString()
        {
            return "invalid input data";
        }
    }

    // **********************************************************************************************
    // Structs for exchanging Data with RFBSurface
    // **********************************************************************************************
    /// <summary> this struct represents the data of a remote framebuffer </summary>
    public struct ServerData
    {
        /// <summary> the width of the framebuffer </summary>
        public ushort fbWidth;
        /// <summary> the height of the framebuffer </summary>
        public ushort fbHeight;
        /// <summary> the pixelformat of the framebuffer </summary>
        public PixelFormat pixForm;
        /// <summary> the name of the server </summary>
        public string serverName;
    }

    /// <summary> this struct represents a vnc-pixelformat </summary>
    public struct PixelFormat
    {
        /// <summary> the bits used for a pixel in the stream </summary>
        public byte bitsPerPixel;
        /// <summary> the depth of this pixelformat e.g. 32bit</summary>
        public byte depth;
        /// <summary> is pixeldata encoded in bigendian format </summary>
        public byte bigEndian;
        /// <summary> is this pixelformat a truecolor format or only a palette format </summary>
        public byte trueColor;
        /// <summary> the maximal value the red component can reach </summary>
        public ushort redMax;
        /// <summary> the maximal value the green component can reach </summary>
        public ushort greenMax;
        /// <summary> the maximal value the blue component can reach </summary>
        public ushort blueMax;
        /// <summary> the bit shift needed to accesss the red component </summary>
        public byte redShift;
        /// <summary> the bit shift needed to accesss the greeen component </summary>
        public byte greenShift;
        /// <summary> the bit shift needed to accesss the blue component </summary>		
        public byte blueShift;
    }


}